#include "t_box_context.h"
#include "input/input.h"

namespace BoxPlayer //[class T_Box_Core_Global_Stream]
{
    class T_Box_Core_Global_Stream : public T_Box_Context
    {
    public:
        explicit T_Box_Core_Global_Stream(T_Box_Core *a_core)
            : T_Box_Context(a_core)
        {  }
    protected:
        virtual void run();
    };
};

namespace BoxPlayer //[class T_Box_Core]
{
    class T_Box_CorePrivate : public QObject
    {
    protected:
        T_DECLARE_PUBLIC(T_Box_Core)
    protected:
                T_Box_Seek_Manager m_seek_mgr;
        T_Box_Sequence *m_sequence;
        T_Box_Control *m_global_control;
        T_Box_Core_Global_Stream *m_global_stream;
    public:
        explicit T_Box_CorePrivate(T_Box_Core *me, T_Box_Sequence *a_sequence)
            : T_INIT_QPTR(me)
            , m_sequence(a_sequence)
            , m_global_control(0)
            , m_global_stream(0)
        {
        }
        virtual ~T_Box_CorePrivate()
        {
        }
    };
    T_Box_Core::T_Box_Core(T_Box_Sequence *a_sequence)
    {
        T_INIT_DPTR(T_Box_Core)(this, a_sequence);
        Q_D(T_Box_Core);
        d->m_global_control = new T_Box_Control(this);
        d->m_seek_mgr.setContentDuration(d->m_global_control->contentDuration());
        d->m_global_stream = new T_Box_Core_Global_Stream(this);
        d->m_global_stream->start();
    }
    T_Box_Core::~T_Box_Core()
    {
        Q_D(T_Box_Core);
        d->m_global_stream->finish();
        delete d->m_global_control;
    }
    bool T_Box_Core::isValid()
    {
        Q_D(T_Box_Core);
        return d->m_global_control->isValid();
    }
    T_Box_Control *T_Box_Core::globalControl()
    {
        Q_D(T_Box_Core);
        return d->m_global_control;
    }
    T_Box_Sequence *T_Box_Core::sequence()
    {
        Q_D(T_Box_Core);
        return d->m_sequence;
    }
    T_Clock *T_Box_Core::timeline()
    {
        return seekManager()->timeline();
    }
    T_Box_Seek_Manager *T_Box_Core::seekManager()
    {
        Q_D(T_Box_Core);
        return &d->m_seek_mgr;
    }
    void T_Box_Core_Global_Stream::run()
    {
        for(;;)
        {
            if(this->closing())
            {
                break;
            }
            msleep(1);
            QSharedPointer<T_AV_Packet> v_packet(NULL);
            {
                v_packet = this->control()->readPacket(true);
                if(!v_packet)
                {
                    this->core()->seekManager()->registerTiming(LONG_LONG_MAX);
                    break;
                }
                this->core()->seekManager()->registerTiming(v_packet->timing());
            }
        }
    }
};

namespace BoxPlayer //[class T_Box_Control]
{
    class T_Box_ControlPrivate : public QObject
    {
    protected:
        T_DECLARE_PUBLIC(T_Box_Control)
    protected:
                bool m_valid;
        T_Box_Core *m_core;
        T_Box_Input_Sequence *m_input;
        AVFormatContext	*m_format_ctx;
        T_SharedPointerQueue<T_AV_Packet> m_ahead_queue;
        QList<T_Box_Media *> m_media_list;
    public:
        explicit T_Box_ControlPrivate(T_Box_Control *me, T_Box_Core *a_core)
            : T_INIT_QPTR(me)
            , m_valid(false)
            , m_core(a_core)
            , m_input(0)
            , m_format_ctx(0)
        {
        }
        virtual ~T_Box_ControlPrivate()
        {
        }
    };
    T_Box_Control::T_Box_Control(T_Box_Core *a_core)
    {
        T_INIT_DPTR(T_Box_Control)(this, a_core);
        Q_D(T_Box_Control);
        d->m_valid = false;
        d->m_input = new T_Box_Input_Sequence(d->m_core->sequence());
        if(!d->m_input->isValid()) return;
        d->m_format_ctx = d->m_input->getAVFormatContext();
        if(!d->m_format_ctx) return;
        d->m_valid = true;
        for(uint i=0; i<d->m_format_ctx->nb_streams; i++)
        {
            T_Box_Media *v_media = new T_Box_Media(this, i);
            v_media->setParent(this);
            d->m_media_list.append(v_media);
        }
    }
    T_Box_Control::~T_Box_Control()
    {
        Q_D(T_Box_Control);
        if(d->m_format_ctx) ::av_close_input_stream(d->m_format_ctx);
        if(d->m_input) delete d->m_input;
    }
    bool T_Box_Control::isValid()
    {
        Q_D(T_Box_Control);
        return d->m_valid;
    }
    T_Box_Core *T_Box_Control::core()
    {
        Q_D(T_Box_Control);
        return d->m_core;
    }
    AVFormatContext	*T_Box_Control::formatContext()
    {
        Q_D(T_Box_Control);
        return d->m_format_ctx;
    }
    QSharedPointer<T_AV_Packet> T_Box_Control::headPacket(bool a_all_media)
    {
        Q_D(T_Box_Control);
        if(d->m_ahead_queue.size()>0)
        {
            return d->m_ahead_queue.head();
        }
        QSharedPointer<T_AV_Packet> v_obj_packet = this->p_readPacket(a_all_media);
        if(v_obj_packet==NULL)
        {
            return QSharedPointer<T_AV_Packet>(NULL);
        }
        d->m_ahead_queue.enqueue(v_obj_packet);
        return d->m_ahead_queue.head();
    }
    QSharedPointer<T_AV_Packet> T_Box_Control::readPacket(bool a_all_media)
    {
        Q_D(T_Box_Control);
        if(d->m_ahead_queue.size()>0)
        {
            return d->m_ahead_queue.dequeue();
        }
        QSharedPointer<T_AV_Packet> v_obj_packet = this->p_readPacket(a_all_media);
        return v_obj_packet;
    }
    QSharedPointer<T_AV_Packet> T_Box_Control::enqueuePacket()
    {
        Q_D(T_Box_Control);
        if(!this->p_hasOpenMedia())
        {
            return QSharedPointer<T_AV_Packet>(NULL);
        }
        QSharedPointer<T_AV_Packet> v_av_packet;
        while((v_av_packet = this->readPacket(false)))
        {
            if(d->m_media_list[v_av_packet->media()->streamIndex()]->isOpen())
            {
                d->m_media_list[v_av_packet->media()->streamIndex()]->packetQueue()->enqueue(v_av_packet);
                return v_av_packet;
            }
        }
        foreach(T_Box_Media *v_media, d->m_media_list)
        {
            if(v_media->isOpen() && !v_media->packetQueue()->isComplete())
            {
                v_media->packetQueue()->enqueue(QSharedPointer<T_AV_Packet>(new T_AV_Packet(v_media, NULL)));
            }
        }
        return QSharedPointer<T_AV_Packet>(NULL);
    }
    qint64 T_Box_Control::seekMe(T_Box_Seek_Manager *a_seek_mgr, qint64 a_msec)
    {
        Q_UNUSED(a_seek_mgr);
        Q_D(T_Box_Control);
        int v_ret = avformat_seek_file(
                this->formatContext(),
                -1,
                LONG_LONG_MIN,
                a_msec * AV_TIME_BASE / 1000,
                LONG_LONG_MAX,
                0);
        if(v_ret<0)
        {
            return -1;
        }
        d->m_ahead_queue.clear();
        qint64 v_result = a_msec;
        if(QSharedPointer<T_AV_Packet> v_av_packet = this->headPacket())
        {
            v_result = v_av_packet->timing();
        }
        foreach(T_Box_Media *v_media, d->m_media_list)
        {
            v_media->packetQueue()->clear();
            v_media->packetCount() = 0;
        }
        return v_result;
    }
    qint64 T_Box_Control::contentDuration()
    {
        Q_D(T_Box_Control);
        if(!d->m_format_ctx) return 0;
        return  ((qint64)1000) * d->m_format_ctx->duration / AV_TIME_BASE;
    }
    T_Box_Media *T_Box_Control::openMedia(AVMediaType a_media_type)
    {
        Q_D(T_Box_Control);
        Q_ASSERT(a_media_type==AVMEDIA_TYPE_AUDIO||a_media_type==AVMEDIA_TYPE_VIDEO);
        foreach(T_Box_Media *v_media, d->m_media_list)
        {
            if(v_media->mediaType() != a_media_type) continue;
            if(v_media->open()) return v_media;
        }
        return NULL;
    }
    QList<T_Box_Media *> &T_Box_Control::mediaList()
    {
        Q_D(T_Box_Control);
        return d->m_media_list;
    }
    bool T_Box_Control::p_hasOpenMedia()
    {
        Q_D(T_Box_Control);
        foreach(T_Box_Media *v_media, d->m_media_list)
        {
            if(v_media->isOpen()) return true;
        }
        return false;
    }
    QSharedPointer<class T_AV_Packet> T_Box_Control::p_readPacket(bool a_all_media)
    {
        Q_D(T_Box_Control);
        if(!a_all_media && !this->p_hasOpenMedia())
        {
            return QSharedPointer<T_AV_Packet>(NULL);
        }
        AVPacket v_av_packet;
        for(;;)
        {
            if(::av_read_frame(d->m_format_ctx, &v_av_packet)<0)
            {
                return QSharedPointer<T_AV_Packet>(NULL);
            }
            if(!a_all_media && !d->m_media_list[v_av_packet.stream_index]->isOpen())
            {
                continue;
            }
            T_Box_Media *v_media = d->m_media_list[v_av_packet.stream_index];
            QSharedPointer<T_AV_Packet> v_obj_packet = QSharedPointer<T_AV_Packet>(new T_AV_Packet(v_media, &v_av_packet));
            v_obj_packet->frameNo() = v_media->packetCount()++;
            return v_obj_packet;
        }
        Q_ASSERT(0);
    }
};

namespace BoxPlayer //[class T_Box_Media]
{
    class T_Box_MediaPrivate : public QObject
    {
    protected:
        T_DECLARE_PUBLIC(T_Box_Media)
    protected:
                bool m_open;
        T_Box_Control *m_control;
        int m_index;
        AVStream *m_stream;
        AVMediaType m_media_type;
        AVCodecContext *m_codec_ctx;
        AVCodec *m_codec;
        T_AVFrame *m_frame;
        T_AV_Packet_Queue m_packet_queue;
        qint64 m_packet_count;
    public:
        explicit T_Box_MediaPrivate(T_Box_Media *me, T_Box_Control *a_control, int a_index)
            : T_INIT_QPTR(me)
            , m_open(false)
            , m_control(a_control)
            , m_index(a_index)
            , m_stream(0)
            , m_media_type(AVMEDIA_TYPE_UNKNOWN)
            , m_codec_ctx(0)
            , m_codec(0)
            , m_frame(0)
            , m_packet_queue(me)
            , m_packet_count(0)
        {
            m_stream = m_control->formatContext()->streams[m_index];
            m_media_type = m_stream->codec->codec_type;
            m_codec_ctx = m_stream->codec;
        }
        virtual ~T_Box_MediaPrivate()
        {
            delete m_frame;
            if(m_open)
            {
                ::avcodec_close(m_codec_ctx);
            }
        }
        bool open()
        {
            if(m_open)
            {
                return true;
            }
            if(m_media_type!=AVMEDIA_TYPE_AUDIO && m_media_type!=AVMEDIA_TYPE_VIDEO)
            {
                return false;
            }
            QString v_media_type_str = m_media_type==AVMEDIA_TYPE_AUDIO ? "audio" : "video";
            if(m_media_type==AVMEDIA_TYPE_AUDIO)
            {
                if(m_codec_ctx->channels > 0)
                {
                    m_codec_ctx->request_channels = FFMIN(2, m_codec_ctx->channels);
                }
                else
                {
                    m_codec_ctx->request_channels = 2;
                }
            }
            if(m_media_type==AVMEDIA_TYPE_VIDEO)
            {
                m_frame = new T_AVFrame(m_codec_ctx);
            }
            m_codec=::avcodec_find_decoder(m_codec_ctx->codec_id);
            if(!m_codec)
            {
                qDebug() << QString("Unsupported %1 codec.").arg(v_media_type_str);
                return false;
            }
            if(::avcodec_open(m_codec_ctx, m_codec)<0)
            {
                qDebug() << QString("avcodec_open (%1) failed.").arg(v_media_type_str);
                return false;
            }
            m_open = true;
            return true;
        }
    };
    T_Box_Media::T_Box_Media(T_Box_Control *a_control, int a_index)
    {
        T_INIT_DPTR(T_Box_Media)(this, a_control, a_index);
    }
    T_Box_Media::~T_Box_Media()
    {
    }
    enum AVMediaType T_Box_Media::mediaType()
    {
        Q_D(T_Box_Media);
        return d->m_media_type;
    }
    bool T_Box_Media::open()
    {
        Q_D(T_Box_Media);
        return d->open();
    }
    bool T_Box_Media::isOpen()
    {
        Q_D(T_Box_Media);
        return d->m_open;
    }
    T_Box_Core *T_Box_Media::core()
    {
        Q_D(T_Box_Media);
        return d->m_control->core();
    }
    T_Box_Control *T_Box_Media::control()
    {
        Q_D(T_Box_Media);
        return d->m_control;
    }
    int T_Box_Media::streamIndex()
    {
        Q_D(T_Box_Media);
        return d->m_index;
    }
    AVStream *T_Box_Media::stream()
    {
        Q_D(T_Box_Media);
        return d->m_stream;
    }
    AVCodecContext *T_Box_Media::codecContex()
    {
        Q_D(T_Box_Media);
        return d->m_codec_ctx;
    }
    AVCodec *T_Box_Media::codec()
    {
        Q_D(T_Box_Media);
        return d->m_codec;
    }
    T_AVFrame *T_Box_Media::frame()
    {
        Q_D(T_Box_Media);
        return d->m_frame;
    }
    T_AV_Packet_Queue *T_Box_Media::packetQueue()
    {
        Q_D(T_Box_Media);
        return &d->m_packet_queue;
    }
    void T_Box_Media::flushBuffers()
    {
        Q_D(T_Box_Media);
        ::avcodec_flush_buffers(d->m_codec_ctx);
    }
    bool T_Box_Media::isAudio()
    {
        return (this->mediaType()==AVMEDIA_TYPE_AUDIO);
    }
    bool T_Box_Media::isVideo()
    {
        return (this->mediaType()==AVMEDIA_TYPE_VIDEO);
    }
    T_AVRational T_Box_Media::time_base()
    {
        return stream()->time_base;
    }
    T_AVRational T_Box_Media::avg_frame_rate()
    {
        return stream()->avg_frame_rate;
    }
    T_AVRational T_Box_Media::r_frame_rate()
    {
        return stream()->r_frame_rate;
    }
    qint64 &T_Box_Media::packetCount()
    {
        Q_D(T_Box_Media);
        return d->m_packet_count;
    }
};

namespace BoxPlayer //[class T_Box_Context]
{
    class T_Box_ContextPrivate : public QObject
    {
    protected:
        T_DECLARE_PUBLIC(T_Box_Context)
                T_Box_Core *m_core;
        T_Box_Control *m_control;
        T_Box_Media *m_audio;
        T_Box_Media *m_video;
        qint64 m_max_packet_timing;
    public:
        explicit T_Box_ContextPrivate(T_Box_Context *me, T_Box_Core *a_core)
            : T_INIT_QPTR(me)
            , m_core(a_core)
            , m_control(0)
            , m_audio(0)
            , m_video(0)
            , m_max_packet_timing(0)
        {
            m_control = new T_Box_Control(m_core);
        }
        virtual ~T_Box_ContextPrivate()
        {
            if(m_control) delete m_control;
        }
    };
    T_Box_Context::T_Box_Context(T_Box_Core *a_core)
    {
        T_INIT_DPTR(T_Box_Context)(this, a_core);
    }
    T_Box_Context::~T_Box_Context()
    {
        this->finish();
    }
    T_Box_Core *T_Box_Context::core()
    {
        Q_D(T_Box_Context);
        return d->m_core;
    }
    T_Box_Control *T_Box_Context::control()
    {
        Q_D(T_Box_Context);
        return d->m_control;
    }
    T_Box_Media *T_Box_Context::audio()
    {
        Q_D(T_Box_Context);
        return d->m_audio;
    }
    T_Box_Media *T_Box_Context::video()
    {
        Q_D(T_Box_Context);
        return d->m_video;
    }
    bool T_Box_Context::openAudio()
    {
        Q_D(T_Box_Context);
        if(!d->m_audio) d->m_audio = d->m_control->openMedia(AVMEDIA_TYPE_AUDIO);
        return !!d->m_audio;
    }
    bool T_Box_Context::openVideo()
    {
        Q_D(T_Box_Context);
        if(!d->m_video) d->m_video = d->m_control->openMedia(AVMEDIA_TYPE_VIDEO);
        return !!d->m_video;
    }
    QSharedPointer<T_AV_Packet> T_Box_Context::headPacket(bool a_all_media)
    {
        Q_D(T_Box_Context);
        return d->m_control->headPacket(a_all_media);
    }
    QSharedPointer<T_AV_Packet> T_Box_Context::readPacket(bool a_all_media)
    {
        Q_D(T_Box_Context);
        return d->m_control->readPacket(a_all_media);
    }
    QSharedPointer<T_AV_Packet> T_Box_Context::enqueuePacket()
    {
        Q_D(T_Box_Context);
        return d->m_control->enqueuePacket();
    }
    qint64 T_Box_Context::seekMe(T_Box_Seek_Manager *a_seek_mgr, qint64 a_msec)
    {
        Q_D(T_Box_Context);
        return d->m_control->seekMe(a_seek_mgr, a_msec);
    }
    void T_Box_Context::run()
    {
    }
};
